# This is the module that implements pymq - The Python based Message Queue
# pymq uses the Medusa HTTP server as it's transport layer
#
# Copyright(C) 2010, Dhruv Matani(dhuvbird@gmail.com)
#
# pymq is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# pymq is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with pymq. If not, see
# <http://www.gnu.org/licenses/>.
#

import threading
import asyncore
from request_dispatcher import request_dispatcher
from request_and_response_enqueuer import request_and_response_enqueuer
from data_collector import data_collector
import queue_globals as vars


# This class handles interrupts generated by the response_enqueuer class
class interrupt_handler:
	def __init__(self, owner, request):
		self.owner   = owner
		self.request = request

	def set_terminator(self, terminator):
		self.request.channel.set_terminator(terminator)

	def collect_incoming_data (self, data):
		pass

	def found_terminator(self):
		# Here, we send out all pending responses
		self.set_terminator(1)
		self.owner.send_pending_responses()


class request_handler:
	IDENT = 'pymq request handler'

	def __init__(self):
		pass

	def initialize(self):
		"""
        This method actually initializes the handler
        """
		self.lock = threading.Lock()
		self.pendingResponses = []
		self.pendingRequests  = []

		vars.request_dispatcher = request_dispatcher()
		vars.enqueuer = request_and_response_enqueuer()

	def __repr__ (self):
		return '<%s (%s hits) at %x>' % (
			self.IDENT,
			self.hit_counter,
			id (self)
			)

	# always match, since this is a default
	def match (self, request):
		return 1

	def can_send_response(self, response):
		XSequence = response.get_header("X-Sequence")
		if XSequence:
			# Also set the X-Sequence header here. This is not 100% correct,
			# but we know what we are doing
			response['X-Sequence'] = XSequence
			return True
		else:
			return response._request_id == response.channel.next_response_id

	def send_pending_responses(self):
		self.lock.acquire()
		try:
			if len(self.pendingResponses) == 0 and len(self.pendingRequests) == 0:
				return

			# Process 'em....
			pendingResponses = self.pendingResponses
			pendingRequests  = self.pendingRequests
			self.pendingResponses = []
			self.pendingRequests = []
		finally:
			self.lock.release()

		# First send all the pending requests
		for request in pendingRequests:
			try:
				request.done()
			except Exception, e:
				print "Exception (%s) while sending request" % str(e)
				print asyncore.compact_traceback()

		# Sort the pending responses in order of their _request_id
		pendingResponses.sort(key=lambda request: request._request_id)
		responsesToPutBack = []

		# Note: Even one hung response will block all responses for this channel
		# if the client is a standard HTTP client. A custom client that
		# sets the X-Sequence header will get responses out of order with the
		# X-Sequence header set to the same values as that in the request.
		for response in pendingResponses:
			if not self.can_send_response(response):
				responsesToPutBack.append(response)
				continue

			try:
				# next_response_id is incremented by the done() method
				response.done()
				print "responses sent: %d" % response.channel.next_response_id
			except Exception, e:
				print "Exception (%s) while sending response to '%s'" % (str(e), response.resource)
				print asyncore.compact_traceback()

		# Now, put back all responses that weren't sent
		self.lock.acquire()
		self.pendingResponses.extend(responsesToPutBack)
		self.lock.release()

	def handle_request (self, request):
		# Process any SEND requests if we have to. Also never reply back to
		# any requests on /_ah/processQueue/. This is a HACK URL.
		request.resource = request.split_uri()[0]
		if request.resource == "/_ah/processQueue/":
			if request.collector is None:
				request.collector = interrupt_handler(self, request)
				request.collector.set_terminator(1)
			return

		request.pymq_enqueuer = vars.enqueuer
		cl = request.get_header("Content-Length")
		if cl is not None and int(cl) > 0:
			cl = int(cl)
			# request.channel.set_terminator(cl)
			if not request.collector:
				request.collector = data_collector(vars.request_dispatcher.queueRequest,
												   request)
			request.collector.set_terminator(cl)
		else:
			request.channel.current_request = None
			vars.request_dispatcher.queueRequest(request)
			# request["X-MessageID"] = "1"; request.done()

